<?php

App::import('Vendor','Cms.GigaFieldProperties');
App::import('Model', 'Eav.EavRecord');

class EavBehavior extends ModelBehavior 
{
	/**
	 * Stores eav fields to show in find call
	 * @var unknown_type
	 */
	var $eavQueryFields = array();
	
	public $settings = array();
	
	protected $eavStatuses = array();
	function setup(AppModel $model,$settings = array())
	{
		$default =	array('initCallback' => false, 'dynamicRow' => false,'active'=>true);
		$this->settings[$model->alias] = am($default, $settings);
		$this->setEavStatus($model,$this->settings[$model->alias]['active']);
	}
		
	/**
	 * Supports queries on EAV Fields and handles other functions like pagination
	 */
	function beforeFind($model, $query)
	{
		// if 'field' option is specified
		$simplyConditions = true;
		if(isset($query['eav']))
		{
			$this->setEavStatus($model,$query['eav']);
		}
		if($model->findQueryType != 'count' && isset($query['fields']) && $query['fields'])
		{
			$this->eavQueryFields[$model->alias] = array();
			if(is_array($query['fields']))
			{
				// make sure it contains id - without id afterFind() wont work
				if(!in_array('id', $query['fields']) && !in_array($model->alias.'.id', $query['fields']))
				{
					$query['fields'] = am('id', $query['fields']);
					$this->removeIdInAfterFind[$model->alias] = true;
				}
				if($this->isDynamicRow($model) == false)
				{
					foreach($query['fields'] as $i=>$field)
					{
						if($this->_isEavField($model, $field))
						{
							$this->eavQueryFields[$model->alias][] = $field;
							unset($query['fields'][$i]);
						}
					}
				}
			}
		}
		// in case of no EAV fields manipulation
		if (!isset ($query['conditions']) || empty($query['conditions'])) {
			$simplyConditions = false;
		}
		
		// Basic conditions processing only
		//NOTE: if dynamicRow is true this feature will only work as desired 
		//only if all found rows  have same schema. 
		if($simplyConditions == true)
		{
			$query['conditions'] = $this->_simplifyConditions($model, $query['conditions']);
		}
		return $query;
	}
	
	function _simplifyConditions($model, $conditions = array())
	{
		$fieldProperties = $this->getDynamicSchema($model);
		$conditions = cri('Eav.EavRecord')->parseConditions($model,$fieldProperties,$conditions);
		return $conditions;
	}

	/**
	 * Formats results to include EAV fields as if they are real DB fields
	 */
	function afterFind($model, $results = array (), $primary = false) 
	{
		if($this->isEavActive($model) == false)
		{
			return $results;
		}
		
		// temporary hack for single find
		if (isset ($results[$model->alias])) 
		{
			$tmp = array();
			$tmp[0] = $results;
			$results = $tmp;
		}

		if (isset ($results[0][$model->alias]['id']))
		{
			$results = $this->mergeEavFieldsToResults($model, $results);
		}
		if(isset($this->removeIdInAfterFind[$model->alias]) && $this->removeIdInAfterFind[$model->alias] == true)
		{
			foreach($results as $key => $result)
			{
				unset($results[$key][$model->alias]['id']);
			}
			$this->removeIdInAfterFind[$model->alias] = false;
		}
		
		return $results;
	}


	/**
	 * Merges EAV values into normal model records
	 * @param $results
	 * @return unknown_type
	 */
	function mergeEavFieldsToResults($model, $results)
	{
		if($this->isDynamicRow($model) == false)
		{
			$results = $this->mergeFixedSchemaResults($model,$results);
		}else
		{
			//$results = $this->mergeDynamicSchemaResults($model,$results);
			
			// Info: Above method executes (field types count * no. of records + 1) queries which means
			// no. of queries will grow exponentially when no. of records grow, this experimental method 
			// does the job in max. (field types count + 1) queries. 
			// @todo force proper deletion of eav values on schema alteration parent model
			$results = $this->mergeDynamicSchemaResults_Experimental($model,$results);
		}
		return $results;
	}
	/**
	 * Merges eav values with result for fiexEavSchema fields.
	 * @param unknown_type $model
	 * @param unknown_type $results
	 */
	function mergeFixedSchemaResults($model,$results)
	{
		// fetch ids from array
		$recordIds = Set::extract('{n}.'.$model->alias.'.id', $results);

		// fetch attribute's values
		$eavFields = $this->getDynamicSchema($model)->getSchema();
		if(empty($eavFields))
		{
			return $results;
		}
		
		//$attrValues = $this->fetchAttributeValues($model, $eavAttributes, $recordIds);
		$eavFieldsToFind = array();
		foreach ($eavFields as $field => $fieldDetails)
		{
			if(!isset($this->eavQueryFields[$model->alias]) || (is_array($this->eavQueryFields[$model->alias]) && in_array($field, $this->eavQueryFields[$model->alias])))
			{
				$eavFieldsToFind[$field] = $fieldDetails;
			}
		}
		
		$this->EavRecord = cri('EavRecord');
		$eavRecords = $this->EavRecord->getRecords($model, $recordIds, $eavFieldsToFind);

		// format records
		foreach ($results as $key => $record) 
		{
			$entity_id = $record[$model->alias]['id'];
			foreach ($eavFieldsToFind as $field => $properties)
			{
				if(isset($eavRecords[$entity_id][$field]))
				{
					$eavFieldValue = $eavRecords[$entity_id][$field];
				}
				else
				{
					$eavFieldValue = null;
				}
				$record[$model->alias][$field] = $eavFieldValue;
			}
			$results[$key] = $record;
		}
		return $results;
	}
	/**
	 * Changes schema for each row before finding result.
	 * @param AppModel $model
	 * @param unknown_type $results
	 */
	function mergeDynamicSchemaResults(AppModel $model,$results = array())
	{
		$this->EavRecord = cri('EavRecord');
		foreach($results as $key => $result)
		{
			$eavFields = array();
			try
			{
				$schema = $this->getDynamicSchema($model,$result)->getSchema();
				$eavFields  = $schema;
			}catch(Exception $e)
			{
				$eavFields = array();
				$this->log("Exception::".$e->getMessage(),'eav_exception');
			}
			if(!$eavFields)
			{
				continue;
			}
			$recordId = $result[$model->alias][$model->primaryKey];
			$eavRecords = $this->EavRecord->getRecords($model, array($recordId), $eavFields );
			foreach($eavFields as $eavField => $property)
			{
				$value = null;
				if(isset($eavRecords[$recordId][$eavField]))
				{
					$value = $eavRecords[$recordId][$eavField];
				}
				$result[$model->alias][$eavField] = $value;
			}
			$results[$key] = $result;
		}
		return $results;
	}

	/**
	 * Trying to make a better version of mergeDynamicSchemaResults()
	 * @param AppModel $model
	 * @param unknown_type $results
	 * @return unknown_type
	 */
	function mergeDynamicSchemaResults_Experimental(AppModel $model, $results = array())
	{
		$recordIds = Set::extract('{n}.'.$model->alias.'.id', $results);
		$this->EavRecord = cri('EavRecord');
		$eavRecords = $this->EavRecord->getRecords_Experimental($model, $recordIds, false);
		
		// format records
		foreach ($results as $key => $record) 
		{
			$entity_id = $record[$model->alias]['id'];
			if(isset($eavRecords[$entity_id]))
			{
				$record[$model->alias] = am($record[$model->alias], $eavRecords[$entity_id]);
			}
			$results[$key] = $record;
		}
		return $results;
	}
		
	/**
	 * Returns true if a given field is an EAV field
	 */
	function _isEavField($model, $field) 
	{
		$eavSchema = $this->getDynamicSchema($model);
		
		return $eavSchema->hasField($field);
	}
	
	/**
	 * Before save callback to stop EAV fields going into Model::save()
	 */
	function beforeSave($model)
	{
		$totalEavFields = 0;
		$model->eavData[$model->alias] = array();
		foreach ($model->data[$model->alias] as $field => $val)
		{
			if ($this->_isEavField($model, $field))
			{
				$model->eavData[$model->alias][$field] = $val;
				$totalEavFields++;
				//unset ($model->data[$model->alias][$field]);
			}
		}

		// when saveField() is used or updating single eav field
		$modelKeys = array_keys($model->data[$model->alias]);
		if($totalEavFields == 1 && count($modelKeys) == 2 && in_array('id',$modelKeys))
		{
			$this->saveEavRecord($model, false);
		}
		return true;
	}

	/**
	 * Saves EAV Record
	 */
	function afterSave($model, $created) 
	{
        $FieldProperties = $this->getDynamicSchema($model);
		foreach($model->eavData[$model->alias] as $field => $value)
		{
			if(isset($model->data[$model->alias][$field]))
			{
				$value = $model->data[$model->alias][$field];
			}
            //dirty workaround to video upload field.In case of edit video value becomes empty if not video is uploaded.
            //remove Value from Eav data if empty.
            else if($FieldProperties->getType($field) == "video" && (!is_string($model->eavData[$model->alias][$field]) || !strlen($model->eavData[$model->alias][$field])))
            {
              unset($model->eavData[$model->alias][$field]);
              continue;
            }
			$model->eavData[$model->alias][$field]  = $value;
		}
		$this->saveEavRecord($model, $created);
	}
	
	function saveEavRecord($model, $created)
	{
		if(isset($model->eavData) && !empty($model->eavData))
		{
			$this->_saveEavRecord($model, $created);
		}		
	}
	
	function _saveEavRecord($model, $created)
	{
		$this->EavRecord = cri('EavRecord');
		$recordCreated = false;
		//create new if no record exist.
		if($this->EavRecord->hasAny(array('entity' => $model->alias, 'entity_id' => $model->id)) == true)
		{	
			$eavRecordId = $this->EavRecord->field('id',array('entity' => $model->alias, 'entity_id' => $model->id));
		}else
		{
			$data = array('EavRecord' => array('entity' => $model->alias, 'entity_id' => $model->id));
			$this->EavRecord->create();
			$this->EavRecord->save($data);
			$eavRecordId = $this->EavRecord->id;
		}
		$this->EavRecord->saveValues($model,$model->id,$eavRecordId, $model->eavData[$model->alias], $this->getDynamicSchema($model));
		$model->eavData[$model->alias] = array();
	}

    /**
     *
     * @param AppModel $model
     * @param type $result
     * @return GigaFieldProperties 
     */
	function getDynamicSchema(AppModel $model, $result = null)
	{
		// @todo cache schemas to increase performance
		if($this->hasInitCallback($model) == false)
		{
			return new GigaFieldProperties($model->fieldProperties);
		}
		else
		{
			$FieldProperties = $model->getAttributes($result);
		}

		if(!is_a($FieldProperties, 'GigaFieldProperties'))
		{
			trigger_error('Model: '.$model->alias.'::getAttributes() returned invalid FieldProperties.');
			$FieldProperties = new GigaFieldProperties(array());
			return false;
		}
		return $FieldProperties;
	}
	/**
	 * Merges model fieldproperties
	 * @param AppModel $model
	 */
	function refreshFieldProperties(AppModel $model)
	{
		$oldEavFields = $model->getFields('eav_field','1');
		if($oldEavFields)
		{
			$oldEavFields = array_combine($oldEavFields,$oldEavFields);
			$model->fieldProperties = array_diff_key($model->fieldProperties,$oldEavFields);
			$model->validate = array_diff_key($model->validate,$oldEavFields);
		}
		$FieldPropertiesObject = $this->getDynamicSchema($model); 
		$fieldProperties = $FieldPropertiesObject->getFieldProperties();
		$validationRules = $FieldPropertiesObject->getValidation();
		foreach($fieldProperties as $field => $property)
		{
			$validationRule = array();
			if(isset($validationRules[$field]))
			{
				$validationRule = $validationRules[$field];
			}
			$property['eav_field'] = '1';
			$model->setFieldProperties($field,$property);
			$model->addValidationRule($field,$validationRule);
		}
	}
	
	function hasInitCallback(AppModel $model)
	{
		return isset($this->settings[$model->alias]['initCallback']) && $this->settings[$model->alias]['initCallback'] == true;
	}
	/**
	 * Returns whether eav config is for dynamic row or not.
	 * @param AppModel $model
	 */
	function isDynamicRow(AppModel $model)
	{
		return $this->settings[$model->alias]['dynamicRow'];
	}
	
	function setEavStatus($model,$isActive)
	{
		$this->eavStatuses[$model->alias] = $isActive;
	}
	
	function isEavActive($model)
	{
		return $this->eavStatuses[$model->alias];
	}
}
?>